package logic

import (
	"context"
	"strings"
	"time"

	"mall/common/enumx"
	"mall/common/modelx"
	"mall/common/resultx"
	"mall/service/blog/model"
	"mall/service/blog/rpc/internal/svc"
	"mall/service/blog/rpc/types/blog"

	"github.com/zeromicro/go-zero/core/logx"
)

type BasicSearchLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewBasicSearchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BasicSearchLogic {
	return &BasicSearchLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BasicSearchLogic) BasicSearch(in *blog.BasicSearchReq) (*blog.BasicSearchResp, error) {
	// logx.Infof("BasicSearchReq: %+v ", in)
	// Offset:20 Limit:10 Preload:\"blogBook:Id,Title,Slug;blogMedia:Uid,Id,Url,Thumbnail,Order;blogBookRelation:brand\" EagerLoad:\"blogBook:blogMedia,blogTerm,blogBookRelation\" Filter:\"perfume,brand\" Search:\"Acqua di\" VisitorId:10001 PlatformId:1

	if in.Preload == "" || in.EagerLoad == "" {
		return nil, resultx.StatusError(resultx.REUQEST_PARAM_ERROR, "")
	}

	if in.Search == "" {
		return nil, resultx.StatusError(resultx.ENTRY_NO_SEARCH_TERM_SPECIFIED, "")
	}

	preloadMap, eagerLoadMap := modelx.GetPreloadMap(in.Preload, in.EagerLoad)
	// logx.Infof("preloadMap: %+v ", preloadMap)
	// map[blogBook:[Id Title Slug] blogBookRelation:[brand] blogMedia:[Uid Id Url Thumbnail Order] blogTerm:[]]
	// logx.Infof("eagerLoadMap: %+v ", eagerLoadMap)
	// map[blogBook:[blogMedia blogTerm blogBookRelation]]

	blogBookEntityName := model.RpcEntityNames.BlogBook
	_, hasSettingMap := model.RpcEntityPreloadMap[blogBookEntityName] // BlogKnowledgeBasis的关联字段
	if !hasSettingMap {
		return &blog.BasicSearchResp{}, nil
	}

	blogBookRepositoryName := model.RepositoryNames.BlogBook
	columnMap, hasColumnMap := model.RepositoryPreloadMap[blogBookRepositoryName] // BlogKnowledgeBasis的数据库字段
	if !hasColumnMap {
		return &blog.BasicSearchResp{}, nil
	}

	blogBookRelationEntityName := model.RpcEntityNames.BlogBookRelation
	blogBookRelationSettingMap, hasBlogBookRelationSettingMap := model.RpcEntityPreloadMap[blogBookRelationEntityName]
	if !hasBlogBookRelationSettingMap {
		return &blog.BasicSearchResp{}, nil
	}

	var sectionIds []interface{}
	sectionMap := make(map[uint64]string) // BlogBookRelation 的 id、 别名 map
	for _, s := range blogBookRelationSettingMap {
		if s.Id > 0 {
			if _, exist := sectionMap[s.Id]; !exist {
				sectionMap[s.Id] = s.Name
				if strings.Contains("," + in.Filter + ",", "," + s.Name + ",") {
					sectionIds = append(sectionIds, s.Id)
				}
			}
		}
	}

	var selectCols []string
	if preloadFields, hasPreloadFields := preloadMap[blogBookEntityName]; hasPreloadFields {
		selectColMap := make(map[string]string)
		for _, field := range preloadFields {
			if _, exist := selectColMap[field]; !exist {
				if column, hasColumn := columnMap[field]; hasColumn {
					selectColMap[field] = column
				} else {
					selectColMap[field] = ""
				}
			}
		}
		for _, column := range selectColMap {
			if column != "" {
				selectCols = append(selectCols, column)
			}
		}
	}

	var count int64
	var offset int32
	var err error
	var entryList []*model.BlogKnowledgeBasis
	var filterMap map[string]map[string][]interface{}
	if in.PreFilter != "" {
		filterMap = modelx.GetFilterMap(in.PreFilter, columnMap)
		logx.Infof("filterMap: %+v ", filterMap)
		// filterMap: map[]
	}
	if filterMap == nil {
		filterMap = make(map[string]map[string][]interface{})
	}
	filterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]interface{}{
		"equalTo": []interface{}{enumx.PostStatusPublish},
	}
	orderBy := modelx.GetOrderBy(in.Sorter, columnMap)
	logx.Infof("orderBy: %s ", orderBy)
	// orderBy: 
	var cnt int32
	var eids []interface{} // []model.BlogKnowledgeBasis.ID(已去重)
	oidMap := make(map[uint64]int) // Map: model.BlogKnowledgeBasis.ID => int
	for {
		offset = in.Limit * cnt
		cnt += 1

		var tmpList []*model.BlogKnowledgeBasis
		tmpList, count, err = l.svcCtx.BlogBook.GetList(l.ctx, model.M{
			"offset":  in.Offset + offset,
			"limit":   in.Limit,
			"orderBy": orderBy,
			"search":  in.Search,
			"select":  model.BlogKnowledgeBasisColumns.ID,
		}, 3, filterMap)
		if err != nil {
			return nil, err
		}

		if len(tmpList) == 0 { // 没有记录
			break
		}

		if count <= 1 { // 没有下一页
			break
		}

		var oids []interface{} // []model.BlogTermRelationship.ObjectID(已去重)
		for i, tmp := range tmpList {
			if _, exist := oidMap[tmp.ID]; !exist {
				oids = append(oids, tmp.ID)
				oidMap[tmp.ID] = i
			}
		}

		if len(oids) == 0 { // 没有新记录
			continue
		}

		postfilterMap := make(map[string]map[string][]interface{})
		postfilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]interface{}{
			"in": oids,
		}
		postfilterMap[model.BlogTermRelationshipColumns.TermID] = map[string][]interface{}{
			"in": sectionIds,
		}
		relationshipList, _, listErr := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{
			"select": model.BlogTermRelationshipColumns.ObjectID,
		}, -1, postfilterMap)
		if listErr != nil {
			return nil, listErr
		}

		if len(relationshipList) == 0 { // 没有新记录
			continue
		}

		for _, relationship := range relationshipList {
			eids = append(eids, relationship.ObjectID)
		}

		if len(eids) >= int(in.Limit) { // 已有足够的记录数
			break
		}
	}

	if len(eids) > 0 {
		filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]interface{}{
			"in": eids,
		}
		entryList, _, err = l.svcCtx.BlogBook.GetList(l.ctx, model.M{
			"orderBy": orderBy,
			"select":  strings.Join(selectCols, ","),
		}, -1, filterMap)
		if err != nil {
			return nil, err
		}
	}

	var entries []*blog.BookDetailResp
	var searchLogId int64
	if len(entryList) > 0 {
		if in.VisitorId > 0 && in.PlatformId > 0 {
			searchLogId = l.svcCtx.BlogUser.InsertSearchLog(l.ctx, &model.BlogUserSearchLog{
				UserID:     uint64(in.VisitorId),
				PlatformID: uint(in.PlatformId),
				Keyword:    in.Search,
				CreatedAt:  time.Now(),
			})
		}

		var ids []interface{} // []model.BlogKnowledgeBasis.ID
		for _, entry := range entryList {
			ids = append(ids, entry.ID)
		}

		subEntitySettingMap := make(map[string]map[string]*model.ColumnSetting)
		if eagerLoadEntities, hasEagerLoadEntities := eagerLoadMap[blogBookEntityName]; hasEagerLoadEntities {
			for _, entity := range eagerLoadEntities {
				if entitySettingMap, hasEntitySettingMap := model.RpcEntityPreloadMap[entity]; hasEntitySettingMap {
					subEntitySettingMap[entity] = entitySettingMap
				}
			}
		}

		idCategoryMap := make(map[uint64][]*blog.BlogCategory) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogCategory
		idTagMap := make(map[uint64][]*blog.BlogTag) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogTag
		idSectionMap := make(map[uint64][]*blog.BlogCategory) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogCategory
		blogTermEntityName := model.RpcEntityNames.BlogTerm
		blogTermRepositoryName := model.RepositoryNames.BlogTerm
		if _, hasEntitySettingMap := subEntitySettingMap[blogTermEntityName]; hasEntitySettingMap {
			subPreloadFields, hasSubPreloadFields := preloadMap[blogTermEntityName]
			subColumnMap, hasSubColumnMap := model.RepositoryPreloadMap[blogTermRepositoryName]
			if hasSubPreloadFields && hasSubColumnMap {
				subFilterMap := make(map[string]map[string][]interface{})
				subFilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]interface{}{
					"in": ids,
				}
				subFilterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]interface{}{
					"equalTo": []interface{}{enumx.PostTypeKnowledgeBase},
				}
				relationshipList, _, listErr := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{}, -1, subFilterMap)
				if listErr != nil {
					return nil, listErr
				}

				if len(relationshipList) > 0 {
					selectColMap := make(map[string]string)
					for _, field := range subPreloadFields {
						if _, exist := selectColMap[field]; !exist {
							if column, hasColumn := subColumnMap[field]; hasColumn {
								selectColMap[field] = column
							} else {
								selectColMap[field] = ""
							}
						}
					}
					var subSelectCols []string
					for _, column := range selectColMap {
						if column != "" {
							subSelectCols = append(subSelectCols, column)
						}
					}

					tidMap := make(map[uint64][]uint64) // Map: model.BlogTerm.TermID => []model.BlogKnowledgeBasis.ID
					var tids []interface{} // []model.BlogTerm.TermID(已去重)
					for _, relationship := range relationshipList {
						if _, exist := tidMap[relationship.TermID]; !exist {
							tids = append(tids, relationship.TermID)
						}
						tidMap[relationship.TermID] = append(tidMap[relationship.TermID], relationship.ObjectID)
					}

					termFilterMap := make(map[string]map[string][]interface{})
					termFilterMap[model.BlogTermColumns.TermID] = map[string][]interface{}{
						"in": tids,
					}
					termList, _, listErr := l.svcCtx.BlogTerm.GetList(l.ctx, model.M{
						"select": strings.Join(subSelectCols, ","),
					}, -1, termFilterMap)
					if listErr != nil {
						return nil, listErr
					}

					if len(termList) > 0 {
						for _, term := range termList {
							var section string
							if s, exist := sectionMap[term.TermID]; exist {
								section = s
							}
							if oids, exist := tidMap[term.TermID]; exist {
								switch enumx.TermTaxonomy(term.Taxonomy) {
								case enumx.TermTaxonomyCategory:
									oidMap := make(map[uint64]struct{})
									for _, oid := range oids {
										if _, ok := oidMap[oid]; !ok {
											idCategoryMap[oid] = append(idCategoryMap[oid], &blog.BlogCategory{
												Id:   int64(term.TermID),
												Name: term.Name,
												Slug: term.Slug,
											})
											oidMap[oid] = struct{}{}
										}
									}
								case enumx.TermTaxonomyTag:
									oidMap := make(map[uint64]struct{})
									for _, oid := range oids {
										if _, ok := oidMap[oid]; !ok {
											idTagMap[oid] = append(idTagMap[oid], &blog.BlogTag{
												Id:   int64(term.TermID),
												Name: term.Name,
												Slug: term.Slug,
											})
											oidMap[oid] = struct{}{}
										}
									}
								case enumx.TermTaxonomyKnowledgeBase:
									oidMap := make(map[uint64]struct{})
									for _, oid := range oids {
										if _, ok := oidMap[oid]; !ok {
											idSectionMap[oid] = append(idSectionMap[oid], &blog.BlogCategory{
												Id:   int64(term.TermID),
												Name: term.Name,
												Slug: term.Slug,
												Uri:  section,
											})
											oidMap[oid] = struct{}{}
										}
									}
								}
							}
						}
					}
				}
			}
		}

		idMediaMap := make(map[uint64][]*blog.BlogMedia) // Map: model.BlogKnowledgeBasis.ID => *blog.BlogMedia
		blogMediaEntityName := model.RpcEntityNames.BlogMedia
		blogMediaRepositoryName := model.RepositoryNames.BlogMedia
		if _, hasEntitySettingMap := subEntitySettingMap[blogMediaEntityName]; hasEntitySettingMap {
			subPreloadFields, hasSubPreloadFields := preloadMap[blogMediaEntityName]
			subColumnMap, hasSubColumnMap := model.RepositoryPreloadMap[blogMediaRepositoryName]
			if hasSubPreloadFields && hasSubColumnMap {
				selectColMap := make(map[string]string)
				for _, field := range subPreloadFields {
					if _, exist := selectColMap[field]; !exist {
						if column, hasColumn := subColumnMap[field]; hasColumn {
							selectColMap[field] = column
						} else {
							selectColMap[field] = ""
						}
					}
				}
				var subSelectCols []string
				for _, column := range selectColMap {
					if column != "" {
						subSelectCols = append(subSelectCols, column)
					}
				}
				mediaFilterMap := make(map[string]map[string][]interface{})
				mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectID] = map[string][]interface{}{
					"in": ids,
				}
				mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectType] = map[string][]interface{}{
					"equalTo": []interface{}{enumx.PostTypeKnowledgeBase},
				}
				mediaFilterMap[model.BlogAttachmentRelationshipColumns.AttachmentThumbnail] = map[string][]interface{}{
					"equalTo": []interface{}{1},
				}
				mediaList, _, listErr := l.svcCtx.BlogBook.GetMedia(l.ctx, model.M{
					"orderBy": model.BlogAttachmentRelationshipColumns.AttachmentOrder,
					"select": strings.Join(subSelectCols, ","),
				}, -1, mediaFilterMap) // 读取关联表
				if listErr != nil {
					return nil, listErr
				}

				if len(mediaList) > 0 {
					for _, media := range mediaList {
						idMediaMap[media.ObjectID] = append(idMediaMap[media.ObjectID], &blog.BlogMedia{
							Uid:       int64(media.AttachmentRelationshipID),
							Id:        int64(media.AttachmentID),
							Uri:       media.AttachmentSource,
							Thumbnail: int32(media.AttachmentThumbnail),
							Order:     int32(media.AttachmentOrder),
							Url:       media.AttachmentImageURL,
							AltText:   media.AttachmentImageAlt,
							Meta:      media.AttachmentImageMeta,
						})
					}
				}
			}
		}

		idMetaMap := make(map[uint64]*blog.BlogMeta) // Map: model.BlogKnowledgeBasis.ID => *blog.BlogMeta
		blogBookMetaEntityName := model.RpcEntityNames.BlogBookMeta
		if entitySettingMap, hasEntitySettingMap := subEntitySettingMap[blogBookMetaEntityName]; hasEntitySettingMap {
			if subPreloadFields, hasSubPreloadFields := preloadMap[blogBookMetaEntityName]; hasSubPreloadFields {
				var metaKeys []interface{}
				for _, field := range subPreloadFields {
					fieldSetting, hasFieldSetting := entitySettingMap[field]
					if hasFieldSetting && fieldSetting.Name != "" {
						metaKeys = append(metaKeys, fieldSetting.Name)
					}
				}
				if len(metaKeys) > 0 {
					metaFilterMap := make(map[string]map[string][]interface{})
					metaFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]interface{}{
						"in": ids,
					}
					metaFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]interface{}{
						"in": metaKeys,
					}
					metaList, _, listErr := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{}, -1, metaFilterMap)
					if listErr != nil {
						return nil, listErr
					}

					if len(metaList) > 0 {
						for _, metum := range metaList {
							if metum.MetaKey != "" && metum.MetaValue.Valid {
								if _, exist := idMetaMap[metum.KnowledgeBaseID]; !exist {
									idMetaMap[metum.KnowledgeBaseID] = &blog.BlogMeta{
										Id: int64(metum.MetaID),
										Key: metum.MetaKey,
										Value: metum.MetaValue.String,
									}
								}
							}
						}
					}
				}
			}
		}

		idEntriesMap := make(map[uint64][]*blog.BlogEntry) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogEntry
		blogBookRelationRepositoryName := model.RepositoryNames.BlogBookRelation
		if _, hasEntitySettingMap := subEntitySettingMap[blogBookRelationEntityName]; hasEntitySettingMap {
			subPreloadFields, hasSubPreloadFields := preloadMap[blogBookRelationEntityName]
			subColumnMap, hasSubColumnMap := model.RepositoryPreloadMap[blogBookRelationRepositoryName]
			if hasSubPreloadFields && hasSubColumnMap {
				preloadMap := make(map[string]int)
				selectColMap := make(map[string]string)
				for idx, field := range subPreloadFields {
					if _, exist := preloadMap[field]; !exist { // 确定relation的key及entry的字段
						preloadMap[field] = idx
						if column, hasColumn := subColumnMap[field]; hasColumn {
							selectColMap[field] = column
						} else {
							selectColMap[field] = ""
						}
					}
				}

				var relationIds []interface{}
				for _, s := range blogBookRelationSettingMap {
					if s.Id > 0 {
						if _, exist := preloadMap[s.Name]; exist {
							relationIds = append(relationIds, s.Id)
						}
					}
				}
				if len(relationIds) > 0 { // relation 的 section id 数组不为空
					var subSelectCols []string
					for _, column := range selectColMap {
						if column != "" {
							subSelectCols = append(subSelectCols, column)
						}
					}

					relationFilterMap := make(map[string]map[string][]interface{})
					relationFilterMap[model.BlogKnowledgeBaseRelationshipColumns.FromEntryID] = map[string][]interface{}{
						"in": ids,
					}
					relationFilterMap[model.BlogKnowledgeBaseRelationshipColumns.ToSectionID] = map[string][]interface{}{
						"in": relationIds,
					}
					relationSelectCols := []string{
						model.BlogKnowledgeBaseRelationshipColumns.FromEntryID,
						model.BlogKnowledgeBaseRelationshipColumns.ToEntryID,
						model.BlogKnowledgeBaseRelationshipColumns.ToSectionID,
						model.BlogKnowledgeBaseRelationshipColumns.ToEntryGroup,
						model.BlogKnowledgeBaseRelationshipColumns.ToEntryOrder,
						model.BlogKnowledgeBaseRelationshipColumns.ToEntryMeta,
					}
					relationList, _, listErr := l.svcCtx.BlogBook.GetRelationshipList(l.ctx, model.M{
						"select":  strings.Join(relationSelectCols, ","),
						"orderBy": model.BlogKnowledgeBaseRelationshipColumns.ToEntryOrder,
					}, -1, relationFilterMap)
					if listErr != nil {
						return nil, listErr
					}

					if len(relationList) > 0 {
						idRelationsMap := make(map[uint64][]*model.BlogKnowledgeBaseRelationship)
						tidMap := make(map[uint64]int) // tids 去重 Map: model.BlogKnowledgeBasis.ID => int
						var tids []interface{}
						for idx, relation := range relationList {
							if _, exist := tidMap[relation.ToEntryID]; !exist {
								tidMap[relation.ToEntryID] = idx
								tids = append(tids, relation.ToEntryID)
							}
							idRelationsMap[relation.FromEntryID] = append(idRelationsMap[relation.FromEntryID], relation)
						}
						if len(tids) > 0 {
							entryFilterMap := make(map[string]map[string][]interface{})
							entryFilterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]interface{}{
								"in": tids,
							}
							entryFilterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]interface{}{
								"equalTo": []interface{}{enumx.PostStatusPublish},
							}
							var entrySelectCols []string
							if len(subSelectCols) > 0 {
								entrySelectCols = subSelectCols[:]
							} else {
								entrySelectCols = []string{
									model.BlogKnowledgeBasisColumns.ID,
									model.BlogKnowledgeBasisColumns.PostTitle,
									model.BlogKnowledgeBasisColumns.PostName,
									model.BlogKnowledgeBasisColumns.GUID,
								}
							}
							relationEntryList, _, listErr := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
								"select": strings.Join(entrySelectCols, ","),
							}, -1, entryFilterMap)
							if listErr != nil {
								return nil, listErr
							}

							relationEntryMap := make(map[uint64]*model.BlogKnowledgeBasis)
							for _, e := range relationEntryList {
								relationEntryMap[e.ID] = e
							}
							for id, rs := range idRelationsMap {
								entryMap := make(map[uint64]int) // Map: model.BlogKnowledgeBasis.ID => int
								for i, r := range rs {
									if _, exist := entryMap[r.ToEntryID]; exist {
										continue
									}

									entryMap[r.ToEntryID] = i
									if e, exist := relationEntryMap[r.ToEntryID]; exist {
										var section string
										if s, ok := sectionMap[r.ToSectionID]; ok {
											section = s
										}
										idEntriesMap[id] = append(idEntriesMap[id], &blog.BlogEntry{
											Id:        int64(e.ID),
											Title:     e.PostTitle,
											Slug:      e.PostName,
											Link:      e.GUID,
											SectionId: int64(r.ToSectionID),
											Section:   section,
											GroupId:   int64(r.ToEntryGroup),
											Order:     int64(r.ToEntryOrder),
										})
									}
								}
							}
						}
					}
				}
			}
		}

		for _, entry := range entryList {
			var categories []*blog.BlogCategory
			if terms, exist := idCategoryMap[entry.ID]; exist {
				categories = terms[:]
			}
			var tags []*blog.BlogTag
			if terms, exist := idTagMap[entry.ID]; exist {
				tags = terms[:]
			}
			var sections []*blog.BlogCategory
			if terms, exist := idSectionMap[entry.ID]; exist {
				sections = terms[:]
			}
			var media []*blog.BlogMedia
			if ms, exist := idMediaMap[entry.ID]; exist {
				media = ms[:]
			}
			var es []*blog.BlogEntry
			if res, exist := idEntriesMap[entry.ID]; exist {
				es = res[:]
			}
			entries = append(entries, &blog.BookDetailResp{
				Id:            int64(entry.ID),
				ParentId:      int64(entry.PostParentID),
				Title:         entry.PostTitle,
				Excerpt:       entry.PostExcerpt,
				Status:        int32(entry.PostStatus),
				CommentStatus: int32(entry.CommentStatus),
				Slug:          entry.PostName,
				Uri:           entry.GUID,
				Date:          modelx.FormatTime(entry.PostDate, ""),
				DateGmt:       modelx.FormatTime(entry.PostDateGMT, ""),
				Modified:      modelx.FormatTime(entry.PostModifiedDate, ""),
				ModifiedGmt:   modelx.FormatTime(entry.PostModifiedGMT, ""),
				Order:         int64(entry.MenuOrder),
				CommentCount:  int32(entry.CommentCount),
				Media:         media,
				Categories:    categories,
				Tags:          tags,
				Sections:      sections,
				Relations:     es,
			})
		}
	}

	var hasNextPage int32
	var hasPreviousPage int32
	if count > 1 {
		hasNextPage = 1
		count = count - 2
	}
	if count > 0 {
		hasPreviousPage = 1
	}

	return &blog.BasicSearchResp{
		PerfumeList: entries,
		HasNextPage: hasNextPage,
		HasPreviousPage: hasPreviousPage,
		Offset: in.Offset + offset,
		SearchLogId: searchLogId,
	}, nil
}
